package data

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/elastic/go-elasticsearch/v8/typedapi/types"
	"github.com/go-kratos/kratos/v2/log"
	"github.com/redis/go-redis/v9"
	"golang.org/x/sync/singleflight"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"review-service/internal/biz"
	"review-service/internal/data/model"
	"review-service/internal/data/query"
	"review-service/pkg/snowflake"
	"strconv"
	"strings"
	"time"
)

type reviewRepo struct {
	data *Data
	log  *log.Helper
}

func NewReviewRepo(data *Data, logger log.Logger) biz.ReviewRepo {
	return &reviewRepo{
		data: data,
		log:  log.NewHelper(logger),
	}
}

func (r *reviewRepo) SaveReview(ctx context.Context, review *model.ReviewInfo) (*model.ReviewInfo, error) {
	err := r.data.query.ReviewInfo.WithContext(ctx).Save(review)
	return review, err
}
func (r *reviewRepo) GetReviewByOrderID(ctx context.Context, id int64) ([]*model.ReviewInfo, error) {
	return r.data.query.ReviewInfo.WithContext(ctx).
		Where(r.data.query.ReviewInfo.OrderID.Eq(id)).Find()
}

func (r *reviewRepo) SaveReply(ctx context.Context, reply *model.ReviewReplyInfo) (*model.ReviewReplyInfo, error) {
	//参数校验，是否有过回复
	review, err := r.data.query.ReviewInfo.WithContext(ctx).
		Where(r.data.query.ReviewInfo.ReviewID.Eq(reply.ReviewID)).First()
	if err != nil {
		return nil, err
	}
	if review.HasReply == 1 {
		return nil, errors.New("此评价已回复")
	}
	//水平越权，商家只能回复自己店铺的评价
	fmt.Printf("%v:%v", reply.StoreID, review.StoreID)
	if review.StoreID != reply.StoreID {
		return nil, errors.New("水平越权")
	}
	//更新数据库
	err = r.data.query.Transaction(func(tx *query.Query) error {
		if err := tx.ReviewReplyInfo.WithContext(ctx).Save(reply); err != nil {
			r.log.Errorf("savereply creat reply error: %v", err)
			return err
		}

		if _, err := tx.ReviewInfo.WithContext(ctx).Where(tx.ReviewInfo.ReviewID.
			Eq(reply.ReviewID)).Update(tx.ReviewInfo.HasReply, 1); err != nil {
			r.log.Errorf("savereply updata review error: %v", err)
			return err
		}
		return nil
	})

	return reply, err
}

func (r *reviewRepo) SaveAppeal(ctx context.Context, appeal *model.ReviewAppealInfo) (*model.ReviewAppealInfo, error) {
	//参数校验
	//查询是否已经申诉
	ret, err := r.data.query.ReviewAppealInfo.WithContext(ctx).
		Where(r.data.query.ReviewAppealInfo.ReviewID.Eq(appeal.ReviewID),
			r.data.query.ReviewAppealInfo.StoreID.Eq(appeal.StoreID)).First()
	r.log.Debugf("AppealReview query, ret:%v err:%v", ret, err)
	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, err
	}
	if err == nil && ret.Status > 10 {
		return nil, errors.New("此评价已申诉")
	}
	//查询不到审核过的申诉记录
	//1.有申诉记录，但是状态为待审核则需要更新
	//2.没有申诉记录则需要创建
	appeal.Status = 10 //校验没问题就设置为待审核状态
	if ret != nil {
		appeal.AppealID = ret.AppealID
	} else {
		appeal.AppealID = snowflake.GenID()
	}
	err = r.data.query.ReviewAppealInfo.
		WithContext(ctx).
		Clauses(clause.OnConflict{
			Columns: []clause.Column{
				{Name: "review_id"}, // 当review_id conflict
			},
			DoUpdates: clause.Assignments(map[string]interface{}{ // UPDATE
				"status":     appeal.Status,
				"content":    appeal.Content,
				"reason":     appeal.Reason,
				"pic_info":   appeal.PicInfo,
				"video_info": appeal.VideoInfo,
			}),
		}).
		Create(appeal) // INSERT
	r.log.Debugf("AppealReview, err:%v", err)
	return appeal, err

}

func (r *reviewRepo) CompleteAudit(ctx context.Context, param *biz.AuditAppealParam) error {
	err := r.data.query.Transaction(func(tx *query.Query) error {
		// 申诉表
		if _, err := tx.ReviewAppealInfo.
			WithContext(ctx).
			Where(r.data.query.ReviewAppealInfo.AppealID.Eq(param.AppealID)).
			Updates(map[string]interface{}{
				"status":  param.Status,
				"op_user": param.OpUser,
			}); err != nil {
			return err
		}
		// 评价表
		if param.Status == 20 { // 申诉通过则需要隐藏评价
			if _, err := tx.ReviewInfo.WithContext(ctx).
				Where(tx.ReviewInfo.ReviewID.Eq(param.ReviewID)).
				Update(tx.ReviewInfo.Status, 40); err != nil {
				return err
			}
		}
		return nil
	})
	return err
}

func (r *reviewRepo) ListReviewByUserID(ctx context.Context, userID int64, offset, limit int) ([]*model.ReviewInfo, error) {
	return r.data.query.ReviewInfo.
		WithContext(ctx).
		Where(r.data.query.ReviewInfo.UserID.Eq(userID)).
		Order(r.data.query.ReviewInfo.ID.Desc()).
		Limit(limit).
		Offset(offset).
		Find()
}

func (r *reviewRepo) GetReview(ctx context.Context, reviewID int64) (*model.ReviewInfo, error) {
	return r.data.query.ReviewInfo.
		WithContext(ctx).
		Where(r.data.query.ReviewInfo.ReviewID.Eq(reviewID)).
		First()
}

func (r *reviewRepo) AuditReview(ctx context.Context, param *biz.AuditParam) error {
	_, err := r.data.query.ReviewInfo.
		WithContext(ctx).
		Where(r.data.query.ReviewInfo.ReviewID.Eq(param.ReviewID)).
		Updates(map[string]interface{}{
			"status":     param.Status,
			"op_user":    param.OpUser,
			"op_reason":  param.OpReason,
			"op_remarks": param.OpRemarks,
		})
	return err
}

func (r *reviewRepo) ListReviewByStoreID(ctx context.Context, storeID int64, offset, limit int) ([]*biz.MyReviewInfo, error) {
	//return r.getData1(ctx,storeID,offset,limit) 直接查询ES
	return r.getData2(ctx, storeID, offset, limit) //先查询缓存再查询ES
}
func (r *reviewRepo) getData1(ctx context.Context, storeID int64, offset, limit int) ([]*biz.MyReviewInfo, error) {
	resp, err := r.data.es.Search().Index("review").From(offset).Size(limit).
		Query(&types.Query{
			Bool: &types.BoolQuery{
				Filter: []types.Query{
					{
						Term: map[string]types.TermQuery{
							"storeId": {Value: storeID},
						},
					},
				},
			},
		}).Do(ctx)
	if err != nil {
		return nil, err
	}
	//fmt.Printf("es result total:%v\n", resp.Hits.Total.Value)
	list := make([]*biz.MyReviewInfo, 0, resp.Hits.Total.Value)
	b, _ := json.Marshal(resp.Hits.Hits)
	fmt.Printf("%s\n", b)
	for _, hit := range resp.Hits.Hits {
		tmp := &biz.MyReviewInfo{}
		if err := json.Unmarshal(hit.Source_, tmp); err == nil {
			r.log.Errorf("json.Unmarshal(hit.Source_, tmp) failed err:%v", err)
			continue
		}

		list = append(list, tmp)
	}

	return list, err
}
func (r *reviewRepo) getData2(ctx context.Context, storeID int64, offset, limit int) ([]*biz.MyReviewInfo, error) {
	//先查redis缓存
	//没有再es查询
	//通过singflight 合并并发查询
	key := fmt.Sprintf("review:%d:%d:%d", storeID, offset, limit)
	b, err := r.getDataBySinglefilght(ctx, key)
	if err != nil {
		return nil, err
	}

	hm := new(types.HitsMetadata)
	if err := json.Unmarshal(b, hm); err != nil {
		return nil, err
	}

	// 反序列化
	// 反序列化数据
	// resp.Hits.Hits[0].Source_(json.RawMessage)  ==>  model.ReviewInfo

	list := make([]*biz.MyReviewInfo, 0, hm.Total.Value) // ?
	for _, hit := range hm.Hits {
		tmp := &biz.MyReviewInfo{}
		if err := json.Unmarshal(hit.Source_, tmp); err != nil {
			r.log.Errorf("json.Unmarshal(hit.Source_, tmp) failed, err:%v", err)
			continue
		}
		list = append(list, tmp)
	}
	return list, nil
}

var g singleflight.Group

func (r *reviewRepo) getDataBySinglefilght(ctx context.Context, key string) ([]byte, error) {
	v, err, shared := g.Do(key, func() (interface{}, error) {
		//查询缓存
		data, err := r.getDataFromCache(ctx, key)
		r.log.Debugf("getDataFromCache,data:%v, err:%v\n", data, err)
		if err == nil {
			return data, nil
		}
		if errors.Is(err, redis.Nil) {
			data, err := r.getDataFromES(ctx, key)
			if err == nil {
				return data, r.setCache(ctx, key, data)
			}
			return nil, err
		}
		//查缓存失败,直接结束，不往下传输压力
		return nil, err
	})
	r.log.Debugf("singleflight ret: v:%v err:%v shared:%v", v, err, shared)
	if err != nil {
		return nil, err
	}
	return v.([]byte), nil
}
func (r *reviewRepo) getDataFromCache(ctx context.Context, key string) ([]byte, error) {
	r.log.Debugf("getDataFromCache, key:%v", key)
	return r.data.rdb.Get(ctx, key).Bytes()

}

func (r *reviewRepo) setCache(ctx context.Context, key string, data []byte) error {
	return r.data.rdb.Set(ctx, key, data, time.Second).Err()
}

func (r *reviewRepo) getDataFromES(ctx context.Context, key string) ([]byte, error) {
	value := strings.Split(key, ":")
	if len(value) < 4 {
		return nil, errors.New("invalid key")
	}
	index, storeID, offsetStr, limitStr := value[0], value[1], value[2], value[3]

	limit, err := strconv.Atoi(limitStr)
	if err != nil {
		return nil, err
	}
	offset, err := strconv.Atoi(offsetStr)
	if err != nil {
		return nil, err
	}
	resp, err := r.data.es.Search().Index(index).From(offset).Size(limit).
		Query(&types.Query{
			Bool: &types.BoolQuery{
				Filter: []types.Query{
					{
						Term: map[string]types.TermQuery{
							"storeId": {Value: storeID},
						},
					},
				},
			},
		}).Do(ctx)
	if err != nil {
		return nil, err
	}
	return json.Marshal(resp.Hits)
}
